# ---------game项目用来抓取4399的游戏名字--------

'''  --scrapy基本流程--

1.项目创建
    scrapy startproject 项目名称
/-项目
    /-spiders
        /-__init__
        /-spider的name(爬虫的名字，这里处理解析数据，是爬虫的主体)
    /-项目               # 项目根目录
        /-__init__.
        /-items         # 封装数据的格式
        /-middlewares   # 所有中间件
        /-pipelines     # 所有的管道
        /-settings      # 项目的配置

2.进入项目
    cd项目名称

3.创建爬虫
    项目下面打开终端 scrapy startproject game
    cd game进到game里面去创建爬虫
    scrapy genspider xiao 4399.com(限制抓取域名的范围)
    # 这里会编码报错 UnicodeDecodeError:
     'gbk' codec can't decode byte 0x81 incdposition 0: incomplete multibyte sequence
    # 解决方案找到报错点把encoding改为utf-8
    # try:
    #                 with open(filename, encoding= 'utf-8') as fp:
    #                     self._read(fp, filename)

    Cannot create a spider with the same name as your project

    D:\Python文件\爬虫\scapy\01scrapy\game>scrapy genspider 4399game 4399.com
    Created spider '4399game' using template 'basic' in module:
      game.spiders.a4399game

4.可能需要修改start_url在spiders里面
    1)在spiders的parse(response)里面对数据进行解析
    def parse(self, response):
    response.text
    response.xpath()
    response.css()
    解析数据的时候需要注意，默认xpath()返回的是Selector对象，想要数据必须使用extract()提取数据
    extract() 返回列表
    extract_first() 返回一个数据
    yield 返回数据 -> 交给pipeline进行持久化存储

    # 重写spiders的parse()方法
    # parse默认是用来处理解析的，引擎对下载器的请求后的response（默认）交给spider的parse方法解析
    # 数据解析处理！！！

    yield dic  # 返回数据到引擎给到管道，yield如果返回的数据，直接可以认为是给了管道pipelines
    记住，管道默认是不生效的，需要到settings打开管道
    管道配置，把注释打开就行
    # Configure item pipelines
    # See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
    ITEM_PIPELINES = {  # 数据_管道
        # key（管道的路径）：300（管道的默认优先级），数越小优先级越高
       "game.pipelines.GamePipeline": 300,
    }
    可以有多个管道，可以自定义管道，必须实现def process_item(self, item, spider)，注意管道的命名

5.1在pipeline完成对数据的存储
    class 类名():  #随便取
        ddef process_item(self, item, spider):  # 默认已确定的方法，是处理数据的专用方法 item就是管道传过来的数据
        item： 数据
        spider：爬虫
        return item  # return到下一个管道，否则下一个管道收不到数据

    但是scrapy搞大型的爬虫的不仅仅是简单的字符串数，使用字典来传递数据显然是不够的
    scrapy希望你在items.py自定义一个class的数据结构用来传递数据，这样就不用考虑传递数据时的类型错误

    就在items里面定义自己需要的数据结构，参数为传入的爬虫的数据scrapy.Item
        class CaipiaoItem(scrapy.Item):
        # define the fields for your item here like:
        date_num = scrapy.Field()  # 相当于字典的key
        red_ball = scrapy.Field()
        blue_ball = scrapy.Field()


    # 数据存储的方案
    1.数据存储在csv文件便于数据分析
        with open("文件.csv", mode="a", encoding="utf-8")as f:
        f.write(数据)
    2.数据存储在mysql数据库里面，便于业务逻辑的使用
    3.数据存储在MongoDB数据库中
    4.涉及到文件视频等

    # 我们希望在爬虫打开之前就不断往里面写入数据，在爬虫运行完之后关闭文件
        def open_spider(self, spider):  # 在爬虫爬取数据前就打开文件
        self.f = open("双色球数据.csv", mode="a", encoding="utf-8")

        def close_spider(self, spider):  # 在爬虫结束后就及时关闭文件
        if self.f:  # 文件对象不为空
            self.f.close()
    # 设置写入数据库
        class CaipiaoMySQLPipeline:  # 自定义写入mysql文件的管道

        def open_spider(self, spider):  # 在爬虫爬取数据前就打开文件
           self.connection = pymysql.connect(
               host="localhost",
               port=3306,
               user="root",
               password="h123456",
               database="spider"  # 要连接的数据库名字
           )

        def close_spider(self, spider):  # 在爬虫结束后就及时关闭文件
            if self.connection:
                self.connection.close()  # 如果有连接就关闭数据库的连接

        def process_item(self, item, spider):
            # 存储数据到数据库
            blue_ball = item['blue_ball']
            try:
                cursor = self.connection.cursor()  # 创建数据库连接的游标，负责执行SQL语句
                sql = "insert into caipiao(date_num, red_ball, blue_ball) values (%s, %s, %s)"
                cursor.execute(sql, (item['date_num'], "_".join(item['red_ball']), blue_ball))
                self.connection.commit()
            except:
                self.connection.rollback()  # 如果报错了就回滚
            finally:
                if cursor:
                    cursor.close()
            return item
        最后注意在setings里面设置添加pipelines：添加   "caipiao.pipelines.CaipiaoMySQLPipeline": 300,

5.2.在pipeline完成对数据的存储
    # 获取到新href后应该进行新的网络请求
        # 根据scrapy的运行原理，此处应该对获取的href进行处理，处理成一个请求交给引擎
        yield scrapy.Request(
            url=response.urljoin(href),  # 将resp的url与href拼接成一个完整的子页面链接 resp.urljoin(href)
            method="get",
            callback=self.reparse  # 再创建一个reparse()处理子页面的请求
        )

        break
    def reparse(self, resp, **kwargs):  # 处理子页面的请求

    # 也需要自己在items定义一个数据结构实现数据从spider到pipelines管道的传输
    # 而对图片文件的保存需要class PicSavePipeline(ImagesPipeline):

    # 自定义管道继承scrapyImagesPipeline进行自动下载图片，需要重写三个方法
        # 想要使用ImagesPipeline需要在settings单独配置文件夹用来保存文件

        def get_media_requests(self, item, info):  # 负责下载文件的
            pass
        def file_path(self, request, response=None, info=None, *, item=None):  # 文件下载寻访路径
            pass
        def item_completed(self, results, item, info):  # 返回文件的详细信息
            pass

    # 想要使用ImagesPipeline需要在settings单独配置文件夹用来保存文件
    IMAGES_STORE = "./图片"
    # 在settings文件中 设置 MEDIA_ALLOW_REDIRECTS =True 问题完美解决！
    MEDIA_ALLOW_REDIRECTS =True


6.设置settings.py文件将pipeline进行生效设置
    ITEM_PIPELINE = {
        '管道的路径': 优先级（数越小优先级越高，默认300）
    }

7.运行爬虫项目
    在爬虫spiders打开终端   scrapy crawl 爬虫名字

    运行 4399game spider
    scrapy crawl 4399game（爬虫的名字不是项目名）
    # 设置日志的级别 debug info warning error critical
    LOG_LEVEL = "WARNING"  # WARNING警告及以上的信息可以打印，消除其他不必要的信息

如何处理 UA、cookies、代理、headers、反爬？
    ----在引擎与下载器Downloader（下载器中间件）、引擎与spider（爬虫中间件）处理

8.中间件的使用
    根据scrapy运行流程中所在位置不同分为：
        --下载中间件
        --爬虫中间件
    中间件作用：预处理request对象和response对象
        对header和cookies进行设置和处理
        使用代理IP等
        请求进行定制化操作
    scrapy默认情况下，两个中间件都写在middlewares.py 文件中
    且两个中间件使用方法相同，功能相同，通常使用下载中间件


'''
